﻿using Inovout.Cloud.Data.Blob.Models;
using Inovout.Cloud.Data.Blob.Service;
using Inovout.Cloud.Storage.Models;
using Inovout.Cloud.Storage.Services;
using Inovout.Cloud.Storage.Web.Models;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
using System.Web.Http;

namespace Inovout.Cloud.Storage.Web.Controllers
{
    public class ObjectsController : StorageController
    {
        protected IObjectService objectService;
        protected IBucketService bucketService;
        protected IFileService fileService;
        public ObjectsController()
        {
            this.objectService = ComponentRegistry.Resolve<IObjectService>();
            this.bucketService = ComponentRegistry.Resolve<IBucketService>();
            this.fileService = ComponentRegistry.Resolve<IFileService>();
        }
        public Bucket Bucket { get; private set; }

        protected override void Initialize()
        {
            object bucketName = null;

            if (base.TryGetParameterValue("bucket", out bucketName))
            {
                Bucket = bucketService.FindByName(bucketName.ToString());
                if (Bucket == null)
                {
                    throw new System.ArgumentException(string.Format("{0} Bucket不存在", bucketName));
                }
                base.Project = Bucket.Project;
                //this.Configuration.Formatters.Insert(0,new MultipartRelatedMediaTypeFormatter());
            }
        }

        protected override Inovout.Memberships.Models.Membership GetDataOwner()
        {
            object sourceBucketName = null;
            object destinationBucketName = null;
            if (base.TryGetParameterValue("sourceBucketName", out sourceBucketName) && base.TryGetParameterValue("destinationBucketName", out destinationBucketName))
            {
                Bucket sourceBucket = bucketService.FindByName(sourceBucketName.ToString());
                Bucket destinationBucket = bucketService.FindByName(destinationBucketName.ToString());
                if (sourceBucket == null || destinationBucket == null)
                {
                    throw new System.ArgumentException(string.Format("{0}或{1} Bucket不存在", sourceBucketName, destinationBucketName));

                }

                if (sourceBucket == destinationBucket)
                {
                    return (Inovout.Memberships.Models.Membership)(destinationBucket.Project.Membership);
                }
            }
            else
            {
                return base.GetDataOwner();
            }

            return null;

        }


        //GET https://www.googleapis.com/storage/v1beta2/b/{bucket}/o/{object}
        public virtual async Task<HttpResponseMessage> Get(string storageObject)
        {
            object alt;
            base.TryGetParameterValue("alt", out alt);

            if (alt != null && !string.IsNullOrEmpty(alt.ToString()))
            {
                return await Download(storageObject);
            }
            else
            {
                return this.Request.CreateResponse<ObjectInfo>(BindToViewModel(objectService.Find(this.Bucket, storageObject)));
            }
        }
        //GET https://www.googleapis.com/storage/v1beta2/b/{bucket}/o
        [Route("v1/b/{bucket}/o")]
        public  object Get()
        {
            return new
            {
                items =
                    ((IEnumerable<Object>)objectService.FindAllByBucket(this.Bucket.Id)).Select(b => BindToViewModel(b))
            };
        }
        //DELETE https://www.googleapis.com/storage/v1beta2/b/{bucket}/o/{object}
        [Route("v1/b/{bucket}/o/{storageObject}")]
        public void Delete(string storageObject)
        {
            objectService.Delete(this.Bucket, storageObject);
        }
        //PUT https://www.googleapis.com/storage/v1beta2/b/{bucket}/o/{object}
        [Route("v1/b/{bucket}/o/")]
        public void Put(ObjectInfo storageObject)
        {
            objectService.Save(BindToDomainModel(storageObject));
        }
        private string CombineLocation(params string[] paths)
        {
            string result = paths[0];
            for (int i = 1; i < paths.Length; i++)
            {
                string path = paths[i];
                if (!result.EndsWith("/") && !path.StartsWith("/"))
                {
                    result += "/";
                }
                result += path;
            }
            return result;
        }

        //POST https://www.googleapis.com/storage/v1beta2/b/{bucket}/o
        [HttpPost]
        [Route("upload/v1/b/{bucket}/o/")]
        public async Task<ObjectInfo> Upload(string bucket, string uploadType)
        {
            ObjectInfo storageObject = null;
            if (uploadType.Equals("resumable"))
            {
                storageObject = await this.Request.Content.ReadAsAsync<ObjectInfo>(this.Configuration.Formatters);
                Object bucketObject = BindToDomainModel(storageObject);

                objectService.Save(bucketObject);
                string uploadFileUrlTemplate = this.ControllerContext.Request.RequestUri.Scheme + "://"
                    + this.ControllerContext.Request.RequestUri.Authority;
                uploadFileUrlTemplate = CombineLocation(uploadFileUrlTemplate, "v1/upload/");
                uploadFileUrlTemplate += "?uploadType={0}&upload_id={1}";
                if (Application.Settings.Contains("ObjectUploadUrl"))
                {
                    uploadFileUrlTemplate = Application.Settings["ObjectUploadUrl"].ToString();
                }
                HttpContext.Current.Response.AddHeader("Location",
                 string.Format(uploadFileUrlTemplate, uploadType, bucketObject.File.Key)
                 );
                return BindToViewModel(bucketObject);
            }
            else if (uploadType.Equals("multipart"))
            {
                var multipartContent = await this.Request.Content.ReadAsMultipartAsync(new MultipartRelatedStreamProvider()).ConfigureAwait(false);
                HttpContent objectInfoPartContent = multipartContent.Contents.Single(c => c.Headers.ContentType.MediaType.ToLower().Equals("application/json"));
                HttpContent objectStreamPartContent = multipartContent.Contents.Single(c => !c.Headers.ContentType.MediaType.ToLower().Equals("application/json"));
                storageObject = await objectInfoPartContent.ReadAsAsync<ObjectInfo>().ConfigureAwait(false);
                Object bucketObject = BindToDomainModel(storageObject);

                bucketObject.Length = objectStreamPartContent.Headers.ContentLength.Value;
                objectService.Save(bucketObject);
                using (System.IO.Stream stream = await objectStreamPartContent.ReadAsStreamAsync().ConfigureAwait(false))
                {
                    return await Upload(bucketObject.File, stream).ConfigureAwait(false);

                }
            }
            else if (uploadType.Equals("media"))
            {
                //using (System.IO.Stream stream = await this.Request.Content.ReadAsStreamAsync().ConfigureAwait(false))
                //{
                object bucketName = null;
                object objectName = null;
                base.TryGetParameterValue("bucket", out bucketName);
                base.TryGetParameterValue("name", out objectName);
                storageObject = new ObjectInfo();
                storageObject.Bucket =bucketName.ToString();
                storageObject.Name = objectName.ToString();
                string base64String = await this.Request.Content.ReadAsStringAsync().ConfigureAwait(false);

                Object bucketObject = BindToDomainModel(storageObject);

                objectService.Save(bucketObject);
                byte[] decodedBytes = System.Convert.FromBase64String(base64String);
                using (System.IO.Stream stream = new System.IO.MemoryStream())
                {
                    stream.Write(decodedBytes, 0, decodedBytes.Length);
                    stream.Position = 0;
                    return await Upload(bucketObject.File, stream).ConfigureAwait(false);
                }
                // }
            }
            throw new System.NotSupportedException("只支持resumable和multipart方式创建Object");
        }
        protected async Task<ObjectInfo> Upload(File file, System.IO.Stream stream)
        {
            if (fileService.UploadLocalStream(file.Path, stream))
            {
                return await Complete(file.Key.ToString()).ConfigureAwait(false);
            }
            else
            {
                throw new System.Exception("Upload Failure.");
            }

            //if (!await fileService.UploadAsAsync(file, stream).ConfigureAwait(false))
            //{
            //    throw new System.Exception("Upload Failure.");
            //}


        }


        protected async Task<ObjectInfo> Upload(string upload_id, System.IO.Stream stream)
        {
            if (!await fileService.UploadAsAsync(upload_id, stream).ConfigureAwait(false))
            {
                throw new System.Exception("Upload Failure.");
            }
            return await Complete(upload_id.ToString()).ConfigureAwait(false);

        }

        [Route("v1/b/{bucket}/o/")]
        public ObjectInfo Post(ObjectInfo storageObject)
        {
            Object bucketObject = BindToDomainModel(storageObject);
            objectService.Save(bucketObject);
            return BindToViewModel(bucketObject);
        }
        private string GetContentRangeHeader(long chunkStart, long chunkSize, long streamLength)
        {
            string strLength = streamLength < 0 ? "*" : streamLength.ToString();

            // If a file of length 0 is sent, one chunk needs to be sent with 0 size.
            // This chunk cannot be specified with the standard (inclusive) range header.
            // In this case, use * to indicate no bytes sent in the Content-Range header.
            if (chunkStart == 0 && chunkSize == 0 && streamLength == 0)
            {
                return "bytes */0";
            }
            else
            {
                long chunkEnd = chunkStart + chunkSize - 1;
                return string.Format("bytes {0}-{1}/{2}", chunkStart, chunkEnd, strLength);
            }
        }
        private ObjectInfo BindToViewModel(Object domainObject)
        {
            ObjectInfo storageObject = AutoMapper.Mapper.Map<Object, ObjectInfo>(domainObject);
            return storageObject;

        }
        private Object BindToDomainModel(ObjectInfo viewBucket)
        {
            Object storageObject = AutoMapper.Mapper.Map<ObjectInfo, Object>(viewBucket);
            storageObject.Bucket = this.Bucket;
            object UploadContentLengthHeader;
            if (base.TryGetParameterValue("X-Upload-Content-Length", out UploadContentLengthHeader))
            {
                storageObject.Length = long.Parse(UploadContentLengthHeader.ToString());
            }
            else
            {
                storageObject.Length = -1;
            }
            if (string.IsNullOrEmpty(storageObject.Name))
            {
                storageObject.Name = base.GetParameterValue("name").ToString();
            }
            return storageObject;
        }
        static ObjectsController()
        {
            AutoMapper.Mapper.CreateMap<ObjectInfo, Object>()
                 .ForMember(dest => dest.Bucket, opt => opt.Ignore());

            AutoMapper.Mapper.CreateMap<Object, ObjectInfo>()
                .ForMember(d => d.Bucket, s => s.MapFrom(m => m.Bucket.Name));
        }
        //POST https://www.googleapis.com/storage/v1beta2/b/{sourceBucket}/o/{sourceObject}/copyTo/b/{destinationBucket}/o/{destinationObject}
        [HttpPost]
        [Route("v1/b/{sourceBucketName}/o/{sourceObjectName}/CopyTo/b/{destinationBucketName}/o/{destinationObjectName}")]
        public async Task<ObjectInfo> Copy(string sourceBucketName, string sourceObjectName, string destinationBucketName, string destinationObjectName, ObjectInfo destinationObject)
        {
            if (string.IsNullOrEmpty(destinationObject.Name))
            {
                destinationObject.Name = destinationObjectName;
            }
            if (!destinationObject.Name.Equals(destinationObjectName))
            {
                throw new System.ArgumentException("object name is error.");
            }
            return await
                objectService.CopyAsAsync(sourceBucketName, sourceObjectName, destinationBucketName, BindToDomainModel(destinationObject))
                .ContinueWith<ObjectInfo>(
                t =>
                {
                    return BindToViewModel(t.Result);
                }
                );


        }
        protected async Task<HttpResponseMessage> Download(string storageObject)
        {
            if (this.Request.Headers.Range == null)
            {
                return await objectService.GetStreamAsync(this.Bucket, storageObject).ContinueWith<HttpResponseMessage>(
                    t =>
                    {
                        return CreateResponseWithStream(t.Result);

                    }
                    );

            }
            else
            {
                RangeItemHeaderValue range = this.Request.Headers.Range.Ranges.First();
                return await objectService.GetStreamAsync(Bucket, storageObject,
                    (int)range.From.Value, (int)range.To.Value).ContinueWith<HttpResponseMessage>(
                    t =>
                    {
                        return CreateResponseWithStream(t.Result);

                    }
                    );

            }
        }

        protected HttpResponseMessage DownloadFormLocal(string storageObject) {
           
           System.IO.Stream stream=  objectService.GetLocalStream(this.Bucket, storageObject);
           return  CreateResponseWithStream(stream);
        }


        private HttpResponseMessage CreateResponseWithStream(System.IO.Stream fileStream)
        {
            HttpResponseMessage response = new HttpResponseMessage();
            response.StatusCode = HttpStatusCode.OK;
            response.Content = new StreamContent(fileStream);
            if (this.Request.Headers.Range == null)
            {
                response.Content.Headers.Add("Content-Range",
                    GetContentRangeHeader(0, fileStream.Length - 1, fileStream.Length));
            }
            else
            {
                RangeItemHeaderValue range = this.Request.Headers.Range.Ranges.First();
                response.Content.Headers.Add("Content-Range", GetContentRangeHeader(
                    range.From.Value, range.From.Value + fileStream.Length, fileStream.Length));
            }
            return response;
        }

        public async Task<ObjectInfo> Complete(string upload_id)
        {
            if (await fileService.CompleteUploadAsAsync(upload_id).ConfigureAwait(false))
            {
                string filePath = System.IO.Path.Combine(GetUploadFolderName(), upload_id.ToString());
                if (System.IO.File.Exists(filePath))
                {
                    System.IO.File.Delete(filePath);
                }
                return BindToViewModel(objectService.FindByFile(fileService.FindByKey(upload_id)));
            }
            throw new System.Exception(upload_id + " Not Complete");

        }

        protected string GetUploadFolderName()
        {
            string uploadFolderName = HostingEnvironment.MapPath("~/Upload");
            if (Application.Settings.Contains("UploadFolder"))
            {
                uploadFolderName = Application.Settings["UploadFolder"].ToString();
            }
            if (!System.IO.Directory.Exists(uploadFolderName))
            {
                System.IO.Directory.CreateDirectory(uploadFolderName);
            }

            return uploadFolderName;
        }

    }
}